0. 关于 JavaScript
JavaScript 是单线程语言,其他所谓的“多线程”都是模拟出来的。
1. JavaScript 事件循环
- 为了解决 js 单线程在执行大量耗时代码时的问题,程序员将 js 的任务分为两大类:
- 同步任务
- 进入主线程执行
- 异步任务
- 进入
Event Table
执行 - 当指定的事件完成时,
Event Table
会将这个回调函数移入Event Queue
- 主线程内的任务执行完毕为空,会去
Event Queue
读取对应的函数,进入主线程执行 - 上述过程会不断重复,也就是常说的Event Loop(事件循环)
- 进入
- 同步任务
2 macro-task(宏任务)、micro-task(微任务)
- 除了广义的同步任务和异步任务,其实对任务还有更细致的划分
- macro-task(宏任务):包括整体代码
script
,setTimeout
,setInterval
- micro-task(微任务):
Promise
,process.nextTick
- macro-task(宏任务):包括整体代码
js
事件循环机制,决定了代码执行顺序。
第一步:js
解释器识别所有 js
代码,将同步的代码放到主线程执行;异步的代码放到Event Table
执行。这也是第一次宏任务执行完毕!
第二步:接下来执行所有的微任务。
之后一直循环一、二步骤
3. 举个栗子 :chestnut:
1 | setTimeout(() => console.log('setTimeout-1'), 0) |
- 这段代码作为宏任务,进入主线程
- 先遇到
setTimeout
, 等待0 ms
后,将其回调函数注入到宏任务Event Queue
- 接下来遇到
todo1
函数,没调用,就当看不到 :bowtie: - 调用
todo1
函数 - 遇到
console.log('todo1-await-above')
立即执行并输出 - 遇到
await promise
将等待promise
执行结束后再继续执行,这里将执行权交给todo1
函数外部继续执行 - 遇到
new Promise
立即执行console.log('promise-1')
并输出,之后执行resolve()
,将then
的回调函数注入到微任务Event Queue
- 遇到
console.log('end')
,立即执行并输出 - 注意代码还有
console.log('todo1-await-under')
没有执行,在这里执行并放到微任务Event Queue
【作者译:因为await
后面跟着状态不确定的promise
】 - 好了,整体代码
<script>
作为第一轮的宏任务执行结束,接下来按照先进先出原则,执行微任务队列事件。 - 执行并输出
promise-then-1
- 执行并输出
todo1-await-under
- 检查宏任务队列,这时还有
setTimeout
回调函数需要执行 - 执行并输出
setTimeout-1
- 最后再次检查微任务队列,没有啦。再检查宏任务队列,也没啦。
- 到此结束!
1 | // 输出: |
接下来我们稍微改变一下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19setTimeout(() => console.log('setTimeout-1'))
async function todo1 (params) {
console.log('todo1-await-above')
// await Promise.resolve(99)
await 123 // 改变啦
console.log('todo1-await-under')
}
todo1()
new Promise((resolve, reject) => {
console.log('promise-1')
resolve()
}).then(data => {
console.log('promise-then-1')
})
console.log('end')
上面这段代码做了稍微的改动,将todo1
函数中的await Promise.resolve(99)
更改为await 123
- 老规矩,这段代码作为宏任务,进入主线程
- 先遇到
setTimeout
, 等待0 ms
后,将其回调函数注入到宏任务Event Queue
- 接下来遇到
todo1
函数,没调用,就当看不到 :bowtie: - 调用
todo1
函数 - 遇到
console.log('todo1-await-above')
立即执行并输出 - 遇到
await 123
因为这里await一个具体值,状态是明确的,所以继续向下执行,将console.log('todo1-await-under')
放到微任务队列 - 遇到
new Promise
立即执行console.log('promise-1')
并输出,之后执行resolve()
,将then
的回调函数注入到微任务Event Queue
- 遇到
console.log('end')
,立即执行并输出 - 好了,整体代码
<script>
作为第一轮的宏任务执行结束,接下来按照先进先出原则,先执行微任务队列事件。 - 执行并输出
todo1-await-under
- 执行并输出
promise-then-1
- 检查宏任务队列,这时还有
setTimeout
回调函数需要执行 - 执行并输出
setTimeout-1
- 最后再次检查微任务队列,没有啦。再检查宏任务队列,也没啦。
- 到此结束!
1 | // 输出: |
声明:以上结果在 Google Chrome, 70.0.3538.110 版本 中运行得出